package com.cjl.netty.webcocket;

import com.cjl.netty.model.constant.Constant;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 * <p>
 * 实际进行处理消息的Handler 发送消息 接收消息
 * </p>
 *
 * @author cjl
 * @version 1.0
 * @since 2020-07-30 15:32
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<Object> {


    // 在线通道集合
    public static Set<ChannelHandlerContext> sets = new HashSet<>();

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 传统的HTTP接入
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        }
        // WebSocket接入
        else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {

        if (frame instanceof CloseWebSocketFrame) {
            WebSocketServerHandshaker handsShaker = Constant.webSocketServerHandshakesMap.get(ctx.channel().id().asLongText());
            if (null == handsShaker) {
                sendErrorMessage(ctx, "该用户已经离线或者不存在该连接");
            } else {
                handsShaker.close(ctx.channel(), ((CloseWebSocketFrame) frame).retain());
            }
            return;
        }
        // ping请求
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        if (!(frame instanceof TextWebSocketFrame)) {
            sendErrorMessage(ctx, "不支持二进制文件");
        }
        Channel channel = ctx.channel();
        channel.writeAndFlush(new TextWebSocketFrame("aaa"));

    }

    /**
     * Http协议和转换
     * @param context 处理上下文
     * @param request 消息请求
     */
    private void handleHttpRequest(ChannelHandlerContext context, FullHttpRequest request) {

        if (!request.decoderResult().isSuccess()) {
            sendHttpResponse(context, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        // 协议升级
        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory("ws:/" + context.channel() + "/websocket", null, false);
        WebSocketServerHandshaker handsShaker = factory.newHandshaker(request);
        // 存储握手信息
        Constant.webSocketServerHandshakesMap.put(context.channel().id().asLongText(), handsShaker);
        if (handsShaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(context.channel());
        } else {
            // 表示握手成功
            handsShaker.handshake(context.channel(), request);
            log.info("Http-websocket握手协议升级成功啦");
        }
    }

    /**
     * 消息处理失败 发送一个失败请求 应答客户端
     * @param context 处理上下文
     * @param request 请求
     * @param defaultFullHttpResponse 默认的Http响应
     */
    private void sendHttpResponse(ChannelHandlerContext context, FullHttpRequest request, DefaultFullHttpResponse defaultFullHttpResponse) {
        if (defaultFullHttpResponse.status().code() != HttpResponseStatus.OK.code()) {
            ByteBuf buf = Unpooled.copiedBuffer(defaultFullHttpResponse.status().toString(), CharsetUtil.UTF_8);
            defaultFullHttpResponse.content().writeBytes(buf);
            buf.release();
        }
        // 如果长连接好存在 关闭长连接
        boolean keepLive = HttpUtil.isKeepAlive(request);
        ChannelFuture future = context.channel().writeAndFlush(request);
        if (!keepLive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }

    }

    /**
     * 客户端断开连接之后触发
     * @param ctx 处理和上下文
     * @throws Exception 捕获异常
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        sets.remove(ctx);
    }

    /**
     * 出现移异常后触发
     * @param ctx 处理上下文
     * @param cause 异常类
     * @throws Exception 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 出现不可抗拒因素发送错误消息给客户端
     * @param context 处理上下文
     * @param message 消息文字
     */
    public void sendErrorMessage(ChannelHandlerContext context, String message) {
        context.channel().writeAndFlush(new TextWebSocketFrame(message));
    }
}
